import tkinter as tk
from tkinter import ttk, messagebox, filedialog
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import pandas as pd
import geopandas as gpd
from shapely.geometry import Point
from sqlalchemy import create_engine
import os
import matplotlib.pyplot as plt
import seaborn as sns
import webbrowser
import re
import plotly.express as px
import plotly.graph_objects as go
from matplotlib.colors import Normalize
from matplotlib.cm import ScalarMappable
# 设置字体为支持中文的字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False  # 正确显示负号

# 创建 SQLAlchemy 引擎
engine = create_engine('sqlite:///job_fair123.db')

# 加载数据
preachings_df = pd.read_sql_table('preaching', engine)
companies_df = pd.read_sql_table('company', engine)
positions_df = pd.read_sql_table('position', engine)

# 加载经纬度数据（ adcode_lng_lat.xlsx 在当前工作目录）
lng_lat_df = pd.read_excel('adcode_lng_lat.xlsx')
def classify_company_size(size):
    if not isinstance(size, str) or '人' not in size:
        return None
    
    size = size.replace('人', '')
    
    if '以上' in size:
        if int(size.split('以上')[0]) >= 10000:
            return '大规模（1000人以上）'
        elif int(size.split('以上')[0]) > 500:
            return '中等规模（500-1000人）'
        else:
            return '小规模（500人以下）'
    
    size_range = [int(num) for num in size.split('-') if num.isdigit()]
    if not size_range:
        return None
    
    avg_size = sum(size_range) / len(size_range)
    return '小规模（500人以下）' if avg_size < 500 else '中等规模（500-1000人）' if avg_size <= 1000 else '大规模（1000人以上）'

def plot_company_size_distribution():
    companies_df['company_size'] = companies_df['size'].apply(classify_company_size)
    company_size_categories = pd.Categorical(companies_df['company_size'], 
                                             categories=["小规模（500人以下）", "中等规模（500-1000人）", "大规模（1000人以上）"], 
                                             ordered=True)
    size_counts = companies_df['company_size'].value_counts(sort=False)
    plt.figure(figsize=(8, 6))
    sns.barplot(x=size_counts.index, y=size_counts.values, palette='Blues', order=["小规模（500人以下）", "中等规模（500-1000人）", "大规模（1000人以上）"])
    plt.title('企业规模分布')
    plt.xlabel('企业规模')
    plt.ylabel('企业数量')
    plt.show()
def extract_city(work_location):
    """从 work_location 字段中提取城市名称"""
    if pd.isna(work_location):  # 检查是否为 NaN 或 None
        return "其他"
    elif '-' in work_location:
        return work_location.split('-')[-1].strip()
    else:
        return work_location.strip()
def convert_hiring_num_to_int( hiring_str):
    """将 hiring_num 字段中的招聘人数字符串转换为整数"""
    if pd.isna(hiring_str) or not isinstance(hiring_str, str):
        return None
    hiring_str = re.sub(r'[^\d]', '', hiring_str)  # 移除所有非数字字符
    try:
        return int(hiring_str)
    except ValueError:
        return None
def plot_job_location_distribution():
    # 读取数据表
    companies_df = pd.read_sql_table('company', engine)
    positions_df = pd.read_sql_table('position', engine)

    # 处理工作地点字段并转换招聘人数
    positions_df['city'] = positions_df['work_location'].apply(extract_city)
    positions_df['hiring_num'] = positions_df['hiring_num'].apply(convert_hiring_num_to_int)

    # 筛选掉无法转换的行（即招聘人数为空或无效）
    valid_positions_df = positions_df.dropna(subset=['hiring_num'])

    # 计算每个城市的总招聘人数
    city_hiring_counts = valid_positions_df.groupby('city')['hiring_num'].sum().reset_index(name='total_hiring')

    # 合并职位信息和经纬度信息
    merged_positions_df = pd.merge(valid_positions_df, lng_lat_df, left_on='city', right_on='city', how='left')

    # 创建地理DataFrame之前，先过滤掉“其他”类别的行
    geo_positions_df = merged_positions_df[merged_positions_df['city'] != "其他"].copy()

    # 创建地理DataFrame
    geometry = [Point(xy) for xy in zip(geo_positions_df['lng'], geo_positions_df['lat'])]
    geo_positions_df = gpd.GeoDataFrame(geo_positions_df, geometry=geometry)

    # 将城市总招聘人数合并到 geo_positions_df
    geo_positions_df = geo_positions_df.merge(city_hiring_counts, on='city', how='left')

    # 确保 total_hiring 列为数值类型
    geo_positions_df['total_hiring'] = pd.to_numeric(geo_positions_df['total_hiring'], errors='coerce')

    # 加载省级地图
    script_dir = os.path.dirname(os.path.abspath(__file__))
    provinces_shapefile_path = os.path.join(script_dir, "data", "省.shp")
    if not os.path.exists(provinces_shapefile_path):
        raise FileNotFoundError("地图数据文件未找到")

    provinces = gpd.read_file(provinces_shapefile_path)

    # 设置图形大小和背景色
    fig, ax = plt.subplots(figsize=(10, 8), facecolor='white')

    # 绘制中国省份底图
    provinces.boundary.plot(ax=ax, linewidth=1, color='black')  # 使用边界线代替填充色，增加清晰度

    # 定义一个颜色映射器，用于分配不同的颜色给不同的城市
    cmap1 = plt.get_cmap("tab20")
    cmap2 = plt.get_cmap("tab20b")
    cmap3 = plt.get_cmap("tab20c")

    # 组合多个颜色映射器以获得更多颜色
    colors = list(cmap1.colors) + list(cmap2.colors) + list(cmap3.colors)

    unique_cities = geo_positions_df.drop_duplicates(subset=['city']).sort_values(by='lat', ascending=False)

    # 动态计算注释位置，让箭头指向地图外侧
    side_margin_x = 200  # 调整左右边距
    top_margin_y = 200   # 调整上下边距
    bottom_margin_y = -100
    left_margin_x = -100
    right_margin_x = 100

    left_side_annotations = []
    right_side_annotations = []

    for idx, (x, y, city) in enumerate(zip(unique_cities.geometry.x, unique_cities.geometry.y, unique_cities['city'])):
        total_hiring = unique_cities.loc[unique_cities['city'] == city, 'total_hiring'].values[0]
        if pd.notnull(total_hiring):  # 确保 total_hiring 不为空
            # 根据相对位置调整注释方向
            ha = 'right' if x < geo_positions_df.geometry.x.mean() else 'left'
            offset_x = left_margin_x if ha == 'right' else right_margin_x
            side_annotations = left_side_annotations if ha == 'right' else right_side_annotations
            
            va = 'center'
            offset_y = 0

            side_annotations.append((idx, x, y, city, total_hiring, ha, offset_x, offset_y))

    # 排序以保证注释不会重叠
    left_side_annotations.sort(key=lambda item: item[2], reverse=True)  # 按Y坐标降序排列左侧注释
    right_side_annotations.sort(key=lambda item: item[2], reverse=True)  # 按Y坐标降序排列右侧注释

    all_annotations = left_side_annotations + right_side_annotations

    last_y_positions = {'left': None, 'right': None}
    spacing = 60  # 控制同一侧注释之间的间距

    for idx, x, y, city, total_hiring, ha, offset_x, offset_y in all_annotations:
        side = 'left' if ha == 'right' else 'right'
        last_y = last_y_positions[side]

        if last_y is not None and abs(y - last_y) < spacing:
            offset_y = spacing if side == 'left' else -spacing

        last_y_positions[side] = y + offset_y

        ax.annotate(
            f'{city}\n({total_hiring}个职位)',
            xy=(x, y),
            xytext=(offset_x, offset_y),
            textcoords='offset points',
            ha=ha,
            va='center',
            bbox=dict(boxstyle='round,pad=0.5', fc=colors[idx % len(colors)], alpha=0.8),
            arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0.2', color=colors[idx % len(colors)], linestyle='--'),
            fontsize=9,
            color='black'  # 文字颜色设为黑色，以便在彩色背景下更清晰
        )

    # 添加“其他”类别的注释
    other_jobs = city_hiring_counts[city_hiring_counts['city'] == '其他']
    if not other_jobs.empty:
        total_other_hiring = other_jobs['total_hiring'].values[0]
        ax.annotate(
            f'其他\n({total_other_hiring}个职位)',
            xy=(0, 0),  # 地图右下角的位置
            xycoords='axes fraction',
            xytext=(20, 20),
            textcoords='offset points',
            ha='left',
            va='bottom',
            bbox=dict(boxstyle='round,pad=0.5', fc='orange', alpha=0.8),
            fontsize=9,
            color='black'  # 修改为黑色以适应橙色背景
        )

    # 绘制城市点位，调整点大小和透明度，并使用颜色映射器中的颜色
    for idx, (x, y, city, total_hiring) in enumerate(zip(unique_cities.geometry.x, unique_cities.geometry.y, unique_cities['city'], unique_cities['total_hiring'])):
        if pd.notnull(total_hiring):
            ax.scatter(x, y, s=total_hiring / 10, alpha=0.6, color=colors[idx % len(colors)], marker='o')

    # 移除坐标轴
    ax.set_axis_off()

    # 添加标题
    plt.title('省份、自治区、直辖市职位数图', fontsize=10, pad=8)

    # 自动调整边距以适应所有元素
    plt.tight_layout()

    # 显示图表
    plt.show()
def plot_major_position_distribution( top_n=20, left_margin=0.3):
    positions_df = pd.read_sql_table('position', engine)
    # 将 hiring_num 转换为整数
    positions_df['hiring_num'] = positions_df['hiring_num'].apply(convert_hiring_num_to_int)

    # 初始化一个空的字典用于存储专业及其对应的需求总量
    major_demands = {}
    
    # 遍历数据框中的每一行
    for index, row in positions_df.iterrows():
        majors = row['major_requirements']
        hiring_num = row.get('hiring_num', 1)  # 获取招聘数量，默认为1
        
        if isinstance(majors, str) and pd.notna(hiring_num):  # 确保 hiring_num 不为空
            # 分割并清理专业名称，同时根据招聘数量增加相应的需求量
            for major in majors.split('/'):
                major_cleaned = major.strip()
                if major_cleaned:
                    if major_cleaned in major_demands:
                        major_demands[major_cleaned] += hiring_num
                    else:
                        major_demands[major_cleaned] = hiring_num
    
    # 将字典转换为 Series，并获取前 N 个需求最高的专业
    major_counts = pd.Series(major_demands).sort_values(ascending=False).head(top_n)

    plt.figure(figsize=(8, 6))
    sns.barplot(x=major_counts.values, y=major_counts.index, palette='Blues', dodge=False)
    plt.title('专业与职位需求分布图（前20）')
    plt.xlabel('职位需求总量')
    plt.ylabel('专业')
    
    # 调整左侧边距
    plt.subplots_adjust(left=left_margin)
    
    # 自动调整布局以防止标签被裁剪
    plt.tight_layout()
    
    # 显示图表
    plt.show()
def plot_industry_distribution(top_n=30):
    industries = companies_df['industry'].dropna().str.split('[;/]').explode().str.strip()
    industry_counts = industries.value_counts().head(top_n)
    plt.figure(figsize=(8, 6))
    sns.barplot(x=industry_counts.values, y=industry_counts.index, palette='Blues', dodge=False)
    plt.title('行业分布图（前30）')
    plt.xlabel('企业数量')
    plt.ylabel('行业')
    plt.tight_layout()
    plt.show()

def show_company_distribution():
    """显示企业地区分布"""
    try:
        # 加载公司数据
        companies_df = pd.read_sql_table('company', engine)

        # 统计 location 字段，如果包含 "-" 只统计前半部分
        companies_df['location'] = companies_df['location'].str.split('-').str[0].str.strip()

        # 统计各省市的企业数量
        company_counts = companies_df['location'].value_counts().reset_index()
        company_counts.columns = ['province', 'count']

        # 打印列名以进行调试
        print("Company counts columns:", company_counts.columns.tolist())

        # 加载经纬度映射表
        adcode_lng_lat_path = 'adcode_lng_lat.xlsx'
        if not os.path.exists(adcode_lng_lat_path):
            raise FileNotFoundError(f"文件 {adcode_lng_lat_path} 未找到")
        adcode_lng_lat = pd.read_excel(adcode_lng_lat_path)

        # 打印列名以进行调试
        print("Adcode lng lat columns:", adcode_lng_lat.columns.tolist())

        # 检查是否包含 'city' 列
        if 'city' not in adcode_lng_lat.columns:
            raise KeyError("'city' column not found in adcode_lng_lat DataFrame")

        # 合并数据
        merged_data = pd.merge(company_counts, adcode_lng_lat, left_on='province', right_on='city', how='left')

        # 处理合并后可能存在的缺失值
        merged_data.dropna(subset=['lng', 'lat'], inplace=True)

        # 加载地图数据
        script_dir = os.path.dirname(os.path.abspath(__file__))
        provinces_shapefile_path = os.path.join(script_dir, "data", "省.shp")

        if not os.path.exists(provinces_shapefile_path):
            raise FileNotFoundError("地图数据文件未找到")

        provinces_shapefile = gpd.read_file(provinces_shapefile_path)

        # 创建一个字典来存储省份名称到企业数量的映射
        province_to_count = dict(zip(merged_data['province'], merged_data['count']))

        # 定义颜色映射
        cmap = plt.get_cmap('YlOrRd')  # 使用黄色到红色的渐变色
        norm = Normalize(vmin=0, vmax=max(province_to_count.values()) or 1)  # 避免除以零的情况
        sm = ScalarMappable(cmap=cmap, norm=norm)
        sm.set_array([])

        # 增大画布尺寸
        fig, ax = plt.subplots(figsize=(10,8), constrained_layout=True)

        # 确保地图边界足够宽松，不要过紧地限制坐标轴
        plt.xlim(73.5 - 1, 135.05 + 1)  # 在原有基础上增加一点额外的空间
        plt.ylim(3.86 - 1, 53.56 + 1)

        # 绘制地图，并根据企业数量填充颜色
        for idx, row in provinces_shapefile.iterrows():
            province_name = row['省']
            count = province_to_count.get(province_name, 0)
            color = cmap(norm(count)) if count > 0 else 'white'

            # 使用 GeoPandas 的 plot 方法绘制省份边界
            provinces_shapefile[provinces_shapefile['省'] == province_name].plot(ax=ax, color=color, edgecolor='black')

            # 获取省份多边形的质心坐标用于放置文本
            centroid = row.geometry.centroid
            x, y = centroid.x, centroid.y

            # 只有企业数量不为0时才添加文本
            if count > 0:
                ax.text(
                    x, y,
                    f"{province_name}\n{count}家",
                    ha='center',
                    va='center',
                    fontsize=10,
                    color='green',  # 文字颜色保持黑色以确保可见性
                    bbox=dict(boxstyle='round,pad=0.1', facecolor=color, alpha=0.3)  # 添加背景色框
                )

        plt.title("企业地区分布", fontsize=14)
        plt.colorbar(sm, ax=ax, label='企业数量')
        plt.show()

    except Exception as e:
        print(f"发生错误: {e}")
        # 或者使用图形界面的错误提示框
        import tkinter.messagebox
        tkinter.messagebox.showerror("错误", f"发生错误: {e}")
class JobFairApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("就业宣讲会推荐系统")
        self.geometry("1200x600")

        # 设置科技感主题
        style = ttk.Style()
        style.theme_use('clam')  # 使用更现代的主题

        # 定义颜色
        primary_color = '#3f51b5'  # 深蓝色
        secondary_color = '#64b5f6'  # 浅蓝色
        background_color = '#eaf1f8'  # 浅灰色背景
        text_color = '#2c3e50'  # 深色文本
        highlight_color = '#bbdefb'  # 高亮颜色

        # 配置样式
        style.configure('.', font=('Helvetica', 12), background=background_color, foreground=text_color)
        style.configure('TLabel', font=('Helvetica', 12, 'bold'), foreground=text_color)
        style.configure('TButton', font=('Helvetica', 12), padding=10, relief='flat', background=primary_color, foreground='white', borderwidth=0)
        style.map('TButton', background=[('active', secondary_color), ('pressed', '!disabled', '#1e88e5')])
        style.configure('TFrame', borderwidth=2, relief='solid', background=background_color)
        style.configure('Treeview', rowheight=30, background='#FFFFFF', fieldbackground='#FFFFFF', foreground=text_color)
        style.configure('Treeview.Heading', background=primary_color, foreground='white', font=('Helvetica', 12, 'bold'))
        style.map('Treeview', background=[('selected', secondary_color)], foreground=[('selected', 'white')])
        style.configure('TScrollbar', gripcount=0, background=primary_color, troughcolor=background_color)

        # 创建宣讲会推荐框架
        self.create_recommendation_frame()
        # 创建数据概览框架
        self.create_overview_frame()
        # 初始化时刷新公司类型选项
        self.refresh_company_types()

        self.work_location_var = tk.StringVar()
        self.min_salary_var = tk.StringVar()
        self.max_salary_var = tk.StringVar()
        self.major_requirements_var = tk.StringVar()

        self.display_all_preachings()
        
    def create_recommendation_frame(self):
        recommendation_frame = ttk.LabelFrame(self, text="宣讲会推荐", padding="20")
        recommendation_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True)

        # 左侧筛选条件
        left_frame = ttk.LabelFrame(recommendation_frame, text="筛选条件", padding="20")
        left_frame.pack(side=tk.LEFT, fill=tk.Y, padx=20)

        labels = ["工作地点:", "专业要求:", "最低薪资:", "最高薪资:"]
        entries = []
        self.vars = []

        for idx, label_text in enumerate(labels):
            label = ttk.Label(left_frame, text=label_text)
            label.grid(row=idx, column=0, sticky=tk.W, pady=5)

            var = tk.StringVar()
            entry = ttk.Entry(left_frame, textvariable=var)
            entry.grid(row=idx, column=1, pady=5)
            entries.append(entry)
            self.vars.append(var)

        # 添加公司类型筛选框
        self.company_type_var = tk.StringVar()
        ttk.Label(left_frame, text="公司类型:").grid(row=len(labels), column=0, padx=5, pady=5, sticky=tk.W)
        self.company_type_combobox = ttk.Combobox(
            left_frame, 
            textvariable=self.company_type_var, 
            values=["所有"],
            state='readonly'  # 确保用户只能从下拉列表中选择
        )
        self.company_type_combobox.grid(row=len(labels), column=1, padx=5, pady=5, sticky=tk.W)

        self.entries = entries

        recommend_button = ttk.Button(left_frame, text="推荐宣讲会", command=self.recommend_and_display)
        recommend_button.grid(row=len(labels) + 1, column=0, columnspan=2, pady=10)

        save_button = ttk.Button(left_frame, text="推荐记录保存到本地", command=self.save_recommendations)
        save_button.grid(row=len(labels) + 2, column=0, columnspan=2, pady=10)

        # 右侧推荐宣讲会列表展示
        right_frame = ttk.Frame(recommendation_frame)
        right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)

        scrollbar = ttk.Scrollbar(right_frame)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        self.result_tree = ttk.Treeview(right_frame, columns=("title","organizer", "location", "time", "company_link"), show="headings", yscrollcommand=scrollbar.set)
        self.result_tree.heading("title", text="宣讲会标题")
        self.result_tree.column("organizer",width=0, stretch=tk.NO)
        self.result_tree.heading("location", text="地点")
        self.result_tree.heading("time", text="时间")
        # 链接列为隐藏列
        self.result_tree.column("company_link",width=0, stretch=tk.NO)
        self.result_tree.pack(fill=tk.BOTH, expand=True)

        # 设置列宽度
        self.result_tree.column("title", width=300)
        self.result_tree.column("location", width=180)
        self.result_tree.column("time", width=50)

        self.result_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        scrollbar.config(command=self.result_tree.yview)

        # 绑定双击事件
        self.result_tree.bind("<Double-1>", self.on_tree_double_click)
        
    def create_overview_frame(self):
        overview_frame = ttk.LabelFrame(self, text="数据概览", padding="20")
        overview_frame.pack(side=tk.BOTTOM, fill=tk.X)

        buttons = [
            ("宣讲企业规模分布", plot_company_size_distribution),
            ("热门就业地分布", plot_job_location_distribution),
            ("热门需求专业情况", plot_major_position_distribution),
            ("热门行业情况", plot_industry_distribution),
            ("企业地区分布", show_company_distribution)
        ]

        for i, (button_text, command) in enumerate(buttons):
            button = ttk.Button(overview_frame, text=button_text, command=command)
            button.grid(row=0, column=i, padx=10)
    def refresh_company_types(self):
        # 从 DataFrame 中提取唯一类型的公司，并更新 Combobox 的选项
        try:
            company_types = ["所有"] + list(companies_df['type'].dropna().unique())
            self.company_type_combobox['values'] = company_types
        except Exception as e:
            print(f"Error refreshing company types: {e}")
    def on_tree_double_click(self, event):
        selected_items = self.result_tree.selection()
        if not selected_items:
            return  # 如果没有选中任何项目，则直接返回

        item_id = selected_items[0]
        link = self.result_tree.item(item_id, "values")[4]
        if link:
            webbrowser.open_new_tab(link)
    def recommend_preaching(self, company_type=None, work_location=None, min_salary=None, max_salary=None, major_requirements=None):
        # 使用 preachings_df 的副本以避免修改原始 DataFrame
        filtered_df = preachings_df.copy()

        print(f"Initial number of records: {len(filtered_df)}")

        # 公司类型筛选
        if company_type is not None and company_type != "所有":
            # 找到符合公司类型的公司名称，假设 companies_df 已经被清理过
            filtered_companies = companies_df[
                companies_df['type'].str.contains(company_type, case=False, na=False)
            ]['name'].str.strip()

            # 在宣讲会 DataFrame 中查找符合条件的宣讲会
            filtered_df = filtered_df[filtered_df['organizer'].str.strip().isin(filtered_companies)]
            print(f"After filtering by company type ({company_type}): {len(filtered_df)} records")

        # 工作地点筛选
        if work_location:
            # 找到符合工作地点的公司名称，假设 positions_df 已经被清理过
            filtered_positions = positions_df[
                positions_df['work_location'].str.contains(work_location, case=False, na=False)
            ]['company_name'].str.strip()

            # 在宣讲会 DataFrame 中查找符合条件的宣讲会
            filtered_df = filtered_df[filtered_df['organizer'].str.strip().isin(filtered_positions)]
            print(f"After filtering by work location ({work_location}): {len(filtered_df)} records")

        # 薪资筛选
        if min_salary is not None or max_salary is not None:
            # 提取薪资信息并过滤
            positions_with_salaries = positions_df.dropna(subset=['salary']).copy()
            positions_with_salaries.loc[:, 'min_salary'] = pd.to_numeric(
                positions_with_salaries['salary'].str.extract(r'(\d+)[kK]', expand=False), 
                errors='coerce'
            ) * 1000

            filtered_positions = positions_with_salaries[
                (pd.isna(min_salary) or positions_with_salaries['min_salary'] >= min_salary) &
                (pd.isna(max_salary) or positions_with_salaries['min_salary'] <= max_salary)
            ]

            filtered_df = filtered_df[filtered_df['organizer'].isin(filtered_positions['company_name'])]
            print(f"After filtering by salary range: {len(filtered_df)} records")

        # 专业要求筛选
        if major_requirements:
            filtered_positions = positions_df[
                pd.notna(positions_df['major_requirements']) & 
                positions_df['major_requirements'].str.contains(major_requirements, case=False)
            ]['company_name']

            filtered_df = filtered_df[filtered_df['organizer'].isin(filtered_positions)]
            print(f"After filtering by major requirements: {len(filtered_df)} records")

        # 返回最终筛选结果，去重
        return filtered_df[['title', 'organizer','location', 'time', 'company_link']].drop_duplicates()

    def display_all_preachings(self):
        try:
            company_type = self.company_type_var.get()
            work_location = self.work_location_var.get().strip()
            min_salary_str = self.min_salary_var.get().strip()
            max_salary_str = self.max_salary_var.get().strip()
            major_requirements = self.major_requirements_var.get().strip()

            min_salary = float(min_salary_str) if min_salary_str else None
            max_salary = float(max_salary_str) if max_salary_str else None

            recommendations = self.recommend_preaching(
                company_type=company_type,
                work_location=work_location,
                min_salary=min_salary,
                max_salary=max_salary,
                major_requirements=major_requirements
            )

            for i in self.result_tree.get_children():
                self.result_tree.delete(i)

            if not recommendations.empty:
                for index, row in recommendations.iterrows():
                    self.result_tree.insert("", "end", values=(
                        row.get('title', 'N/A'),
                        row.get('organizer', 'N/A'),
                        row.get('location', 'N/A'), 
                        row.get('time', 'N/A'),
                        row.get('company_link', 'N/A')  # 包含链接
                    ))
            else:
                messagebox.showinfo("结果", "没有找到符合条件的宣讲会。")
        except Exception as e:
            messagebox.showerror("错误", f"发生了一个错误: {e}")
    def recommend_and_display(self):
        try:
            company_type = self.company_type_var.get()
            work_location = self.vars[0].get().strip()
            min_salary_str = self.vars[2].get().strip() or None
            max_salary_str = self.vars[3].get().strip() or None
            major_requirements = self.vars[1].get().strip()

            min_salary = float(min_salary_str) if min_salary_str else None
            max_salary = float(max_salary_str) if max_salary_str else None

            recommendations = self.recommend_preaching(
                company_type=company_type,
                work_location=work_location,
                min_salary=min_salary,
                max_salary=max_salary,
                major_requirements=major_requirements
            )

            for i in self.result_tree.get_children():
                self.result_tree.delete(i)

            if not recommendations.empty:
                for index, row in recommendations.iterrows():
                    self.result_tree.insert("", "end", values=(
                        row.get('title', 'N/A'),
                        row.get('organizer', 'N/A'),
                        row.get('location', 'N/A'), 
                        row.get('time', 'N/A'),
                        row.get('company_link', 'N/A')  # 包含链接
                    ))
            else:
                messagebox.showinfo("结果", "没有找到符合条件的宣讲会。")
        except Exception as e:
            messagebox.showerror("错误", f"发生了一个错误: {e}")
    def save_recommendations(self):
        # 实现保存推荐记录到本地
         # 获取筛选条件
        work_location = self.vars[0].get().strip() or ""
        major_requirements = self.vars[1].get().strip() or ""
        min_salary = self.vars[2].get().strip() or ""
        max_salary = self.vars[3].get().strip() or ""
        company_type = self.company_type_var.get().strip() or ""

        # 构建文件名
        filename_parts = []
        if work_location:
            filename_parts.append(work_location)
        if major_requirements:
            filename_parts.append(major_requirements)
        if min_salary or max_salary:
            salary_range = f"薪资{min_salary}-{max_salary}" if min_salary and max_salary else min_salary or max_salary
            filename_parts.append(salary_range)
        if company_type and company_type != "所有":
            filename_parts.append(company_type)

        filename = "_".join(filename_parts) if filename_parts else "宣讲会"
        filename += ".csv"

        # 确保“推荐记录”目录存在
        output_dir = os.path.join(os.getcwd(), "推荐记录")
        os.makedirs(output_dir, exist_ok=True)

        # 构建完整路径
        file_path = os.path.join(output_dir, filename)

        # 从 Treeview 获取所有推荐记录
        items = self.result_tree.get_children()
        if not items:
            messagebox.showinfo("信息", "没有推荐记录可以保存")
            return
        
        # 提取数据并转换为 DataFrame
        data = []
        for item in items:
            values = self.result_tree.item(item, 'values')
            data.append(values)
        
        if not data:
            messagebox.showinfo("信息", "没有推荐记录可以保存")
            return
        
        df = pd.DataFrame(data, columns=["title", "organizer", "location", "time", "company_link"])

        try:
            df.to_csv(file_path, index=False, encoding='utf-8-sig')  # 使用 utf-8-sig 避免 BOM 问题
            messagebox.showinfo("成功", f"推荐记录已保存到 {file_path}")
        except Exception as e:
            messagebox.showerror("错误", f"保存失败: {e}")

if __name__ == "__main__":
    app = JobFairApp()
    app.mainloop()

